Skip to content

feat(gc): expire keep-tagged exited sessions after a max age - #173

Merged
schickling merged 1 commit into
mainfrom
schickling-assistant/2026-09-04-keep-expiry
Sep 4, 2026
Merged

feat(gc): expire keep-tagged exited sessions after a max age#173
schickling merged 1 commit into
mainfrom
schickling-assistant/2026-09-04-keep-expiry

Conversation

@schickling-assistant

Copy link
Copy Markdown
Contributor

Problem

keep=true exempted a dead session from pty gc's sweep forever. Agents set the tag on the session they are debugging right now (pty run --tag keep=true) and nobody ever comes back to untag it, so the registry becomes append-only. On dev3: 911 sessions, 65 live, 740 EXITED-and-kept — and pty gc --dry-run could only offer to sweep 113. Disk is negligible; the cost is registry noise that every pty ls and every agent-facing listing pays.

Implements decision Q7 (keep=true on an exited session is honored for N days, default 7, then swept). Refs schickling/dotfiles#2412, schickling/dotfiles#2408.

Change

  • pty gc --keep-max-age <dur> (default 7d): a keep-tagged dead session is exempt from step 3's sweep only while it has been dead for less than the window. Grammar is the existing parseDuration (Ns/Nm/Nh/Nd), plus bare 0 meaning "sweep every dead keep session now". A unit-less non-zero value is rejected (--keep-max-age 7 is ambiguous between seconds and days) rather than guessed at.
  • Applies to exited/vanished sessions only. Running sessions were never sweep candidates (isGone gate) and still aren't, at any age or window — covered by a test.
  • Age anchor: exitedAt, falling back to createdAt for a vanished session whose daemon never wrote an exit record — the same precedence pty list --older-than uses. A record with neither timestamp never expires, except under an explicit 0; retaining an unaged record is the recoverable failure.
  • Exit-time retention (shouldReapAtExit) is untouched and still unconditional. Expiry happens only on a gc pass.
  • GcResult gains keepExpired: string[], disjoint from removed, so "the keep tag expired" is reportable apart from ordinary stale-session cleanup. --dry-run previews it and counts it separately (N keep-expired sessions in the summary bar). New public exports: DEFAULT_KEEP_MAX_AGE_MS, isKeepExpired.
  • CLI output: Removed (keep expired after 7d): <name> / Would remove (keep expired after 7d): <name>; the retained line now names the countdown — Kept (keep tag): <name> — swept once dead for 7d, or remove the keep tag to reap it now.
  • Docs: pty gc --help + top-level usage, completions spec (src/completions.ts) with the three checked-in artifacts regenerated, README (lifecycle table, examples, sweep + auto-gc sections), docs/disk-layout.md keep-tag semantics, CHANGELOG under Unreleased.

Gates + evidence

  • npm run typecheck — clean (re-run after rebase onto Make pty kill verify and finish the kill, read the process table in one place, and stop waiting to say a name is taken #170).
  • npx vitest run tests/gc-keep-expiry.test.ts — 8/8 pass. Covers: young keep session retained; expired one swept and reported apart from an identical untagged session; custom window in both --keep-max-age 3h and --keep-max-age=1h spellings; 0 sweeping a just-exited session; createdAt anchoring for a record with no exit record; a running keep session surviving --keep-max-age 0 (verified still status=running afterwards); --dry-run/-n non-mutating in both default and zero windows; flag validation.
  • npx vitest run (full suite, pre-rebase commit) — 1625 passed, 5 failed in 3 files, all environment-bound and none touching gc:
    • tests/completions.test.ts > completes evidence modes and leaf-specific flags in bash — 5s test timeout; the bash completion probe takes ~40s on this host. Proven red at the same base with my changes stashed and dist/ rebuilt: identical single failure, 41s.
    • tests/shells.test.ts > zsh > starts up and accepts commandsexecvp(3) failed.: No such file or directory; zsh is not on PATH in this environment.
    • tests/screenshot.test.ts vim + nano cases — vim reports E1568: Terminal did not respond to DSR request for 'background' color against the headless harness; fails identically when those two files run in isolation.
  • npx vitest run tests/gc*.test.ts tests/help.test.ts tests/completions.test.ts tests/disk-layout-docs.test.ts tests/list-purity.test.ts — green except the pre-existing bash-completion timeout above. The "matches every checked-in completion artifact" parity test passes, so the regenerated completions/pty.{bash,zsh,fish} are in sync.
  • Post-rebase re-run of tests/gc-keep-expiry.test.ts tests/gc.test.ts tests/help.test.ts — green (plus the same pre-existing completions timeout).
  • Manual smoke against a scratch PTY_ROOT: 2-day-old and 30-day-old exited keep sessions → the default pass keeps the young one and reports Removed (keep expired after 7d): old; --keep-max-age 0 sweeps both; dry runs left both files on disk; --keep-max-age 7 exits 1 with the duration error.

Intentionally left

  • No pty gc run on dev3 and no dotfiles pin bump — separate step after merge, per the work brief.
  • No per-session override tag (e.g. keep.max-age=<dur>): nothing asks for per-session retention yet, and the global flag plus pty rm cover both directions.
  • GcResult's doc header still says "Five buckets" (already inaccurate before this change, at 9 buckets) — left alone to keep the diff to one logical change.
Posted on behalf of @schickling
field value
agent_identity dev3.direct.omp.v6c4mkm2
session dev3.v6c4mkm2
agent_persona generalist
agent_supervisor unavailable
agent_tool OMP
agent_tool_version 18.1.2
agent_runtime OMP 18.1.2
tooling_profile dotfiles@7534055

`keep=true` exempted a dead session from `pty gc`'s sweep forever. Agents set
it on the session they are debugging right now and never come back to untag it,
so the registry becomes append-only: 740 of 911 sessions on one host were
exited-and-kept, and `pty gc --dry-run` could only offer to sweep 113.

The exemption is now bounded. `pty gc` skips a `keep`-tagged exited/vanished
session only while it has been dead for less than `--keep-max-age` (default
7d), then sweeps it and reports it under a separate `keepExpired` bucket so the
reason stays visible. `0` sweeps the whole dead-keep backlog on that pass.
Exit-time retention is untouched and still unconditional, and running sessions
are never sweep candidates whatever their age.

Age is anchored on `exitedAt`, falling back to `createdAt` for a `vanished`
session that never wrote one — the same anchor precedence `pty list
--older-than` uses. A record with neither timestamp never expires except under
an explicit `0`.

Refs schickling/dotfiles#2412, schickling/dotfiles#2408.
@schickling-assistant
schickling-assistant marked this pull request as ready for review September 4, 2026 14:33
@schickling
schickling merged commit 86dcc5e into main Sep 4, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants